
#include "ProceduralFishes.hpp"
#include "PerlinNoise.hpp" // For the noise textures

// Tweakable values
static const float NEIGHBORHOOD_SIZE = 8.5f;
static const float SEPARATION_LIMIT = 8.5f;
static const float SEPARATION_FORCE = 15.0f;
static const float FISH_SPEED = 3.0f;

Fish::Fish(AnimatedMesh * mesh, GLuint displayList, FlockOfFishes * flock)
{
	this->fishMesh = mesh;
	this->displayList = displayList;
	this->myFlock = flock;

	// Randomize initial position
	position = Vec3f(((rand()%1000) / 1000.0f) * 20.0f - 10.0f,
					 ((rand()%1000) / 1000.0f) * 20.0f,
					 ((rand()%1000) / 1000.0f) * 20.0f - 10.0f);

	velocity = position;
	velocity.Normalize();
}

void Fish::Update(float deltaTime)
{
	// Get neighboring fishes
	std::vector<Fish *> neighbors;
	myFlock->GetNeighbors(this, NEIGHBORHOOD_SIZE, neighbors);

	// Forces:
	Vec3f acceleration(0.0f, 0.0f, 0.0f);
	Vec3f separationForce(0.0f, 0.0f, 0.0f);
	Vec3f alignmentForce(0.0f, 0.0f, 0.0f);
	Vec3f cohesionForce(0.0f, 0.0f, 0.0f);
	Vec3f toPointForce(0.0f, 0.0f, 0.0f);
	Vec3f floorForce(0.0f, 0.0f, 0.0f);

	const size_t numNeighbors = neighbors.size();

	if (numNeighbors > 0)
	{
		// Calculate neighborhood center
		Vec3f center(0.0f, 0.0f, 0.0f);

		for (size_t i = 0; i < numNeighbors; ++i)
		{
			center += neighbors[i]->position;
		}

		center /= static_cast<float>(numNeighbors);

		// RULE 1: Separation
		for (size_t i = 0; i < numNeighbors; ++i)
		{
			Vec3f vToNeighbor = neighbors[i]->position - position;
			float distToNeightbor = vToNeighbor.Length();

			if (distToNeightbor < SEPARATION_LIMIT)
			{
				// Too close to neighbor
				float force = 1.0f - (distToNeightbor / SEPARATION_LIMIT);
				separationForce -= vToNeighbor * SEPARATION_FORCE * force;
			}
		}

		// RULE 2: Alignment
		for (size_t i = 0; i < numNeighbors; ++i)
		{
			alignmentForce += neighbors[i]->velocity;
		}

		alignmentForce /= static_cast<float>(numNeighbors);

		// RULE 3: Cohesion
		float distToCenter = (center - position).Length() + 0.01f;
		cohesionForce = (center - position) / distToCenter;
	}

	// RULE 4: Steer to point
	toPointForce = Vec3f(0.0f, 10.0f, 0.0f) - position;
	toPointForce.Normalize();
	toPointForce *= 0.5f;

	// RULE 5: Dont crash!
	if (position.y < 4.0f)
	{
		floorForce.y += (4.0f - position.y) * 100.0f;
	}

	// Sum up the forces
	acceleration = separationForce + alignmentForce + cohesionForce + toPointForce + floorForce;

	// Update velocity & position
	acceleration.Normalize();
	velocity += acceleration * deltaTime * 3.0f;
	velocity.Normalize();
	position += velocity * FISH_SPEED * deltaTime;

	// Cap Y position
	position.y = max(position.y, 1.0f);
}

void Fish::Render(float deltaTime)
{
	// Position
	glPushMatrix();
	glTranslatef(position.x, position.y, position.z);

	// Orientation
	glRotatef(MathLib::ArcTangent(velocity.x, velocity.z) * MathLib::RAD_TO_DEG, 0.0f, 1.0f, 0.0f);

	if (!fishMesh)
	{
		// Use a display list for this static mesh, for speed.
		glCallList(displayList);
	}
	else
	{
		// Interpolate meshes between two frames:
		InterpolateAndRenderMesh(fishMesh->keyFrames[fishMesh->currentFrame], fishMesh->keyFrames[fishMesh->nextFrame],
		(deltaTime * fishMesh->frameRate), GfxLib::RENDER_SOLID);
	}

	glPopMatrix();
}

// ===============================================================================================================

FlockOfFishes::FlockOfFishes(const char * cfgFileName) : fish1(0), fish2(0), shark(0), displayList1((GLuint)-1), displayList2((GLuint)-1)
{
	CommLib::INIFile cfgFile;
	int numFishes1, numFishes2, numSharks, i;

	// Texture paramenters for fish1:
	float pTexScale1;
	int pTexWidth1, pTexSeed1;
	int pTexR1, pTexG1, pTexB1;

	// Texture paramenters for fish2:
	float pTexScale2;
	int pTexWidth2, pTexSeed2;
	int pTexR2, pTexG2, pTexB2;

	// Texture paramenters for the shark:
	float pTexScale3;
	int pTexWidth3, pTexSeed3;
	int pTexR3, pTexG3, pTexB3;

	if (!cfgFile.Read(cfgFileName))
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Missing application config file! Using default settings...");

		// Set to defaults:
		numFishes1 = 7;
		numFishes2 = 3;
		numSharks = 1;

		pTexScale1 = 0.5f;
		pTexWidth1 = 12413;
		pTexSeed1 = 63;
		pTexR1 = 10;
		pTexG1 = 10;
		pTexB1 = 150;

		pTexScale2 = 0.2f;
		pTexWidth2 = 12413;
		pTexSeed2 = 83;
		pTexR2 = 0;
		pTexG2 = 200;
		pTexB2 = 0;

		pTexScale3 = 10.0f;
		pTexWidth3 = 12413;
		pTexSeed3 = 63;
		pTexR3 = 255;
		pTexG3 = 255;
		pTexB3 = 255;
	}
	else
	{
		CommLib::DbgPrintf(PRINT_MSG, "Reading fish simulator configs...");

		const CommLib::INISection * sect = cfgFile.GetSection("Fish Config");
		assert(sect != 0);

		sect->GetInteger("NumFishesType1", numFishes1);
		sect->GetFloat("pTexScale1", pTexScale1);
		sect->GetInteger("pTexWidth1", pTexWidth1);
		sect->GetInteger("pTexSeed1", pTexSeed1);
		sect->GetInteger("pTexR1", pTexR1);
		sect->GetInteger("pTexG1", pTexG1);
		sect->GetInteger("pTexB1", pTexB1);

		sect->GetInteger("NumFishesType2", numFishes2);
		sect->GetFloat("pTexScale2", pTexScale2);
		sect->GetInteger("pTexWidth2", pTexWidth2);
		sect->GetInteger("pTexSeed2", pTexSeed2);
		sect->GetInteger("pTexR2", pTexR2);
		sect->GetInteger("pTexG2", pTexG2);
		sect->GetInteger("pTexB2", pTexB2);

		sect->GetInteger("NumSharks", numSharks);
		sect->GetFloat("pTexScale3", pTexScale3);
		sect->GetInteger("pTexWidth3", pTexWidth3);
		sect->GetInteger("pTexSeed3", pTexSeed3);
		sect->GetInteger("pTexR3", pTexR3);
		sect->GetInteger("pTexG3", pTexG3);
		sect->GetInteger("pTexB3", pTexB3);
	}

	// Load models and create display lists:

	if (numFishes1 > 0)
	{
		fish1 = new AnimatedMesh("Fish1/");
		SetNoiseTexture(fish1, "Fish1Tex", pTexScale1, pTexWidth1, pTexSeed1, pTexR1, pTexG1, pTexB1);

		displayList1 = glGenLists(1);
		glNewList(displayList1, GL_COMPILE);
		GfxLib::RenderMesh(fish1->keyFrames[0], GfxLib::RENDER_SOLID);
		glEndList();

		for (i = 0; i < numFishes1; ++i) // Fish 1
		{
			fishes.push_back(new Fish(0, displayList1, this));
		}
	}

	if (numFishes2 > 0)
	{
		fish2 = new AnimatedMesh("Fish2/");
		SetNoiseTexture(fish2, "Fish2Tex", pTexScale2, pTexWidth2, pTexSeed2, pTexR2, pTexG2, pTexB2);

		displayList2 = glGenLists(1);
		glNewList(displayList2, GL_COMPILE);
		GfxLib::RenderMesh(fish2->keyFrames[0], GfxLib::RENDER_SOLID);
		glEndList();

		for (i = 0; i < numFishes2; ++i) // Fish 2
		{
			fishes.push_back(new Fish(0, displayList2, this));
		}
	}

	if (numSharks > 0)
	{
		shark = new AnimatedMesh("Shark/");
		SetNoiseTexture(shark, "SharkTex", pTexScale3, pTexWidth3, pTexSeed3, pTexR3, pTexG3, pTexB3);

		for (i = 0; i < numSharks; ++i) // Sharks
		{
			fishes.push_back(new Fish(shark, (GLuint)-1, this));
		}
	}
}

FlockOfFishes::~FlockOfFishes(void)
{
	const size_t n = fishes.size();

	for (size_t i = 0; i < n; ++i)
	{
		delete fishes[i];
	}

	fishes.clear();

	delete fish1;
	delete fish2;
	delete shark;

	if (displayList1 != (GLuint)-1)
	{
		glDeleteLists(displayList1, 1);
	}

	if (displayList2 != (GLuint)-1)
	{
		glDeleteLists(displayList2, 1);
	}
}

void FlockOfFishes::UpdateFishes(float deltaTime)
{
	// This 2 will be static for now, too slow to animate them in large groups...
	//fish1->Animate(deltaTime);
	//fish2->Animate(deltaTime);

	if (shark != 0)
	{
		shark->Animate(deltaTime);
	}

	const size_t n = fishes.size();

	for (size_t i = 0; i < n; ++i)
	{
		fishes[i]->Update(deltaTime);
	}
}

void FlockOfFishes::RenderFishes(float deltaTime)
{
	const size_t n = fishes.size();

	for (size_t i = 0; i < n; ++i)
	{
		fishes[i]->Render(deltaTime);
	}
}

void FlockOfFishes::GetNeighbors(Fish * fish, float radius, std::vector<Fish *> & neighbors)
{
	const size_t n = fishes.size();

	// Iterate and find all neighbors:
	for (size_t i = 0; i < n; ++i)
	{
		if (fishes[i] != fish)
		{
			if ((fish->position - fishes[i]->position).Length() < radius)
			{
				neighbors.push_back(fishes[i]);
			}
		}
	}
}

void FlockOfFishes::SetNoiseTexture(AnimatedMesh * animMesh, const char * texName,
float scale, int width, int seed, unsigned char r, unsigned char g, unsigned char b)
{
	assert(animMesh != 0);

	CreateNoiseImage(scale, width, seed, r, g, b);

	Mesh * mesh;
	Material * mat;

	for (size_t i = 0; i < animMesh->keyFrames.size(); ++i)
	{
		mesh = animMesh->keyFrames[i];
		MeshLib::Mesh::GroupMap::const_iterator groupIndex = mesh->polyGroups.begin();
		MeshLib::Mesh::GroupMap::const_iterator groupEnd = mesh->polyGroups.end();

		while (groupIndex != groupEnd)
		{
			MeshLib::Mesh::MaterialMap::const_iterator groupMaterial = mesh->materials.find((*groupIndex).second->materialName);
			MeshLib::Mesh::MaterialMap::const_iterator noMaterial = mesh->materials.end();

			if (groupMaterial != noMaterial)
			{
				if (groupMaterial != noMaterial)
				{
					mat = (*groupMaterial).second;

					// Create a noise texture for the mesh
					mat->diffuseMap = new Texture2D(reinterpret_cast<const unsigned char *>(noiseImage), texName,
					noiseImageWidth, noiseImageHeight, 3, GL_RGB, GL_CLAMP_TO_EDGE, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);

					mat->ambientColor = Vec3f(0.0, 0.0, 0.0);
					mat->diffuseColor = Vec3f(0.77, 0.76, 0.76);
					mat->specularColor = Vec3f(0.5, 0.5, 0.5);
					mat->shininess = 96.08;
				}
			}
			++groupIndex;
		}
	}
}